package com.vision.sales.sv.serv.rpc;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.vision.core.cm.data.Domain;
import com.vision.core.cm.exception.HasReferenceException;
import com.vision.core.cm.exception.RpcException;
import com.vision.core.sv.db.mgr.IDataManager;
import com.vision.core.sv.db.mgr.ISimpleCriteria;
import com.vision.core.sv.db.mgr.SimpleCriteria;
import com.vision.core.sv.serv.rpc.AbstractServiceImpl;
import com.vision.core.sv.util.SimpleTransformer;
import com.vision.sales.cm.data.SalesPermissions;
import com.vision.sales.cm.db.data.Product;
import com.vision.sales.cm.db.data.Salesman;
import com.vision.sales.cm.db.data.Salesman.SalesmanStatus;
import com.vision.sales.cm.db.data.SalesmanProduct;
import com.vision.sales.cm.serv.rpc.SalesmanService;

/**
 * 
 * @author Mark
 *
 */
@Service("salesmen")
public class SalesmanServiceImpl extends AbstractServiceImpl<Salesman> implements SalesmanService {

	private static final long serialVersionUID = 1L;
	
	private static final Logger log = LoggerFactory.getLogger(SalesmanServiceImpl.class);
	
	@Autowired
	@Qualifier("productManager")
	private IDataManager<Product> productManager;
	
	@Autowired
	@Qualifier("salesmanProductManager")
	private IDataManager<SalesmanProduct> salesmanProductManager;
	
	public SalesmanServiceImpl() {
		initGilead();
	}

	@Autowired
	@Qualifier("salesmanManager")
	@Override
	public void setDataManager(IDataManager<Salesman> dataManager) {
		super.setDataManager(dataManager);
	}
	
	@Override
	protected Domain getDomain() {
		return Domain.SALESMAN;
	}
	
	// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	@Override
	public List<Salesman> getAll() throws RpcException {
		checkPermissions(SalesPermissions.ViewSalesman);
		
		return super.getAll();
	}

	@Transactional
	@Override
	public Salesman create(Salesman object) throws RpcException {
		checkPermissions(SalesPermissions.CreateSalesman);

		// By default, set to active
		object.setStatus(SalesmanStatus.ACTIVE);
		
		Salesman result = super.create(object);
		
		logCreate(result);
		
		return result;
	}
	
	@Transactional
	@Override
	public Salesman update(Salesman object) throws RpcException {
		checkPermissions(SalesPermissions.UpdateSalesman);
		
		Salesman existing = checkExisting(object.getId());
		Salesman result = super.update(object);
		
		logUpdate(existing, result);
		
		return result;
	}
	
	@Transactional
	@Override
	public Salesman delete(Salesman object) throws RpcException {
		checkPermissions(SalesPermissions.DeleteSalesman);
		
		if (hasReference(object.getId()))
			throw new HasReferenceException("Salesman " + object.getName() + " is being used and referred to by another entity.");
		
		Salesman result = super.delete(object);
		
		logDelete(result);
		
		return result;
	}

	@SuppressWarnings({ "unchecked" })
	@Override
	public List<Salesman> getSalesmenNameAndIdOnly() throws RpcException {
		checkPermissions(SalesPermissions.ViewSalesman);
		
		ISimpleCriteria crit = SimpleCriteria.forClass(Salesman.class);
		String[] properties = new String[]{ "id", "code", "name" };
		crit.properties(properties); // Retrieve only id and name
		crit.asc("name");
		crit.setResultTransformer(SimpleTransformer.create(Salesman.class, properties));
		
		List<Salesman> list = getTemplate().findByCriteria(crit.getDetached());
		return list;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Set<SalesmanProduct> getSalesmanProducts(long salesmanId) throws RpcException {
		checkPermissions(SalesPermissions.ViewSalesman);
		
		Salesman salesman = getDataManager().getById(salesmanId);
		if (salesman == null) {
			log.warn("Salesman (id = " + salesmanId + ") was not found.");
			throw new RpcException("The salesman does not exist.");
		}
		
		// Get the Products first
		List<Product> products = productManager.getAll();
		
		// Approach #1 Using ISimpleCriteria, a simple Facade I created to simplify DetachedCriteria API
		ISimpleCriteria crit = SimpleCriteria.forClass(SalesmanProduct.class);
		crit.eq("salesman.id", salesmanId);
		List<SalesmanProduct> list = getTemplate().findByCriteria(crit.getDetached());
		Map<Long, SalesmanProduct> map = new HashMap<Long, SalesmanProduct>(list.size());
		for (SalesmanProduct sp : list)
			map.put(sp.getProduct().getId(), sp);
				
		LinkedHashSet<SalesmanProduct> set = new LinkedHashSet<SalesmanProduct>(products.size());
		for (Product product : products) {
			SalesmanProduct sp = map.get(product.getId());
			if (sp == null) {
				sp = new SalesmanProduct(); // Create a dummy SalesmanProduct instance
			}
			
			sp.setSalesman(salesman);
			sp.setProduct(product);
			set.add(sp);
		}
		return set;
		
//		// Approach #2 Using native SQL
//		Session session = getTemplate().getSessionFactory().openSession();
//			SQLQuery sql = session.createSQLQuery("SELECT * FROM product FULL OUTER JOIN salesman_product ON salesman_product.product_id = product.id WHERE salesman_id = ?");
//			sql.addEntity(SalesmanProduct.class);
//			sql.setLong(0, salesmanId);
//			
//			List<SalesmanProduct> list = sql.list();
//			Set<SalesmanProduct> set = new HashSet<SalesmanProduct>(list); 
//		session.close();
//		return set;
	}
	
	@Override
	public void saveCommissionRates(List<SalesmanProduct> salesmanProducts) throws RpcException {
		checkPermissions(SalesPermissions.UpdateSalesman);
		
		HibernateTemplate template = salesmanProductManager.getTemplate();
		for (SalesmanProduct salesmanProduct : salesmanProducts)
			template.saveOrUpdate(salesmanProduct);
	}
	
	// Utilities %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
	
	protected boolean hasReference(long salesmanId){
		
		// TODO check if theres a reference. Can't delete
		return false;
	}
	
}
